package com.lagou.edu.mvcframework.servlet;

import com.lagou.edu.mvcframework.annotation.*;
import com.lagou.edu.mvcframework.pojo.Handler;
import com.sun.media.sound.RIFFInvalidDataException;
import org.apache.commons.lang3.StringUtils;

import javax.servlet.ServletConfig;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.File;
import java.io.IOException;
import java.io.InputStream;
import java.lang.annotation.Annotation;
import java.lang.reflect.Field;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.lang.reflect.Parameter;
import java.util.*;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import java.util.stream.Collectors;

/**
 * @Author Lidong Gao
 * @Description
 * @Date 2021/4/30
 **/
public class LgDispatchServlet extends HttpServlet {
    private Properties properties = new Properties();
    private List<String> classNames = new ArrayList<>();
    private Map<String, Object> ioc = new HashMap<>();
    private List<Handler> handlerMapping = new ArrayList<>();

    @Override
    public void init(ServletConfig config) throws ServletException {
        // 1.加载springmvc.xml，初始化配置信息
        String contextConfigLocation = config.getInitParameter("contextConfigLocation");
        doLoadConfig(contextConfigLocation);

        // 2.扫描包下所有类、接口，把包、接口的全路径缓存在List中
        doScan(properties.getProperty("scanPackage"));

        // 3.实例化bean对象，缓存在ioc容器中
        doInstance();

        // 4.注入依赖
        doAutowired();

        // 5.构造HandlerMapping，建立url和method的关系
        initHandlerMapping();

        System.out.println("lagou mvc 初始化完成……");
    }

    private void initHandlerMapping() {
        if (ioc.size() == 0) return;
        for (Map.Entry<String, Object> entry : ioc.entrySet()) {
            Class<?> aClass = entry.getValue().getClass();
            if (!aClass.isAnnotationPresent(LgController.class)) continue;

            String baseUrl = "";
            if (aClass.isAnnotationPresent(LgRequestMapping.class)) {
                LgRequestMapping annotation = aClass.getAnnotation(LgRequestMapping.class);
                baseUrl = annotation.value();
            }

            Method[] methods = aClass.getMethods();
            for (Method method : methods) {
                if (!method.isAnnotationPresent(LgRequestMapping.class)) continue;

                LgRequestMapping annotation = method.getAnnotation(LgRequestMapping.class);
                String methodUrl = annotation.value();
                String url = baseUrl + methodUrl;

                // 封装handler
                Handler handler = new Handler(entry.getValue(), method, Pattern.compile(url));

                LgSecurity lgSecurity = method.getAnnotation(LgSecurity.class);
                if (lgSecurity != null) {
                    handler.setAuths(Arrays.asList(lgSecurity.value()));
                }

                Parameter[] parameters = method.getParameters();
                for (int i = 0; i < parameters.length; i++) {
                    Parameter parameter = parameters[i];
                    if (parameter.getType() == HttpServletRequest.class || parameter.getType() == HttpServletResponse.class) {
                        handler.getParamIndexMapping().put(parameter.getType().getSimpleName(), i);
                    } else {
                        handler.getParamIndexMapping().put(parameter.getName(), i);
                    }
                }
                handlerMapping.add(handler);
            }
        }
    }

    private void doAutowired() {
        if (ioc.size() == 0) return;

        for (Map.Entry<String, Object> entry : ioc.entrySet()) {
            Field[] declaredFields = entry.getValue().getClass().getDeclaredFields();
            for (Field declaredField : declaredFields) {
                if (!declaredField.isAnnotationPresent(LgAutowired.class)) continue;

                // @LgAutowired 没有value，根据当前字段类型注入
                LgAutowired annotation = declaredField.getAnnotation(LgAutowired.class);
                String beanName = annotation.value();
                if ("".equals(beanName)) {
                    beanName = declaredField.getType().getName();
                }

                declaredField.setAccessible(true);
                try {
                    declaredField.set(entry.getValue(), ioc.get(beanName));
                } catch (IllegalAccessException e) {
                    e.printStackTrace();
                }
            }
        }

    }

    private void doInstance() {
        if (classNames.size() == 0) return;
        try {
            for (String className : classNames) {
                // 反射获取对象

                Class<?> aClass = Class.forName(className);
                // 区分LgController、@LgService
                if (aClass.isAnnotationPresent(LgController.class)) {
                    String simpleName = aClass.getSimpleName();
                    ioc.put(lowerFirst(simpleName), aClass.newInstance());
                } else if (aClass.isAnnotationPresent(LgService.class)) {
                    LgService annotation = aClass.getAnnotation(LgService.class);
                    String beanName = annotation.value();
                    if (!"".equals(beanName)) {
                        ioc.put(beanName, aClass.newInstance());
                    } else {
                        ioc.put(lowerFirst(aClass.getSimpleName()), aClass.newInstance());
                    }
                    // 如果Service 有接口，也需要注入
                    Class<?>[] interfaces = aClass.getInterfaces();
                    for (Class<?> anInterface : interfaces) {
                        ioc.put(anInterface.getName(), aClass.newInstance());
                    }
                } else {
                    continue;
                }
            }
        } catch (Exception e) {
            e.printStackTrace();
        }
    }

    private String lowerFirst(String simpleName) {
        char[] chars = simpleName.toCharArray();
        if ('A' <= chars[0] && chars[0] <= 'Z') {
            chars[0] += 32;
        }
        return new String(chars);
    }

    private void doScan(String scanPackage) {
        String packagePath = Thread.currentThread().getContextClassLoader().getResource("").getPath() + scanPackage.replaceAll("\\.", "/");
        File packageFile = new File(packagePath);
        File[] files = packageFile.listFiles();
        for (File file : files) {
            if (file.isDirectory()) {
                doScan(scanPackage + "." + file.getName());
            } else {
                classNames.add(scanPackage + "." + file.getName().replaceAll(".class", ""));
            }
        }
    }

    private void doLoadConfig(String contextConfigLocation) {
        InputStream resourceAsStream = this.getClass().getClassLoader().getResourceAsStream(contextConfigLocation);
        try {
            properties.load(resourceAsStream);
        } catch (IOException e) {
            e.printStackTrace();
        }
    }

    @Override
    protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
        req.setCharacterEncoding("UTF-8");
        resp.setCharacterEncoding("utf-8");
        resp.setContentType("text/html;charset=utf-8");

        Handler handler = getHandler(req);
        if (handler == null) {
            resp.getWriter().write("404 not found");
            return;
        }

        // 绑定参数
        Class<?>[] parameterTypes = handler.getMethod().getParameterTypes();
        Object[] paraValues = new Object[parameterTypes.length];
        // 向参数数组中复制
        Map<String, String[]> parameterMap = req.getParameterMap();

        String[] usernames = parameterMap.get("username");
        if (handler.getAuths().size()>0) {
            if (!handler.getAuths().contains(usernames[0])) {
                resp.getWriter().write("没有权限访问");
                return;
            }
        }

        for (Map.Entry<String, String[]> param : parameterMap.entrySet()) {
            String value = StringUtils.join(Arrays.asList(param.getValue()), ",");
            if (!handler.getParamIndexMapping().containsKey(param.getKey())) continue;
            Integer index = handler.getParamIndexMapping().get(param.getKey());
            paraValues[index] = value;
        }
        Integer reqIndex = handler.getParamIndexMapping().get(HttpServletRequest.class.getSimpleName());
        if (reqIndex != null) {
            paraValues[reqIndex] = req;
        }
        Integer respIndex = handler.getParamIndexMapping().get(HttpServletResponse.class.getSimpleName());
        if (respIndex != null) {
            paraValues[respIndex] = resp;
        }

        // 执行方法
        try {
            handler.getMethod().invoke(handler.getController(), paraValues);
        } catch (IllegalAccessException e) {
            e.printStackTrace();
        } catch (InvocationTargetException e) {
            e.printStackTrace();
        }
    }

    private Handler getHandler(HttpServletRequest req) {
        if (handlerMapping.isEmpty()) return null;

        for (Handler handler : handlerMapping) {
            Matcher matcher = handler.getPattern().matcher(req.getRequestURI());
            if (!matcher.matches()) continue;
            return handler;
        }
        return null;
    }

    @Override
    protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
        this.doPost(req, resp);
    }

}
